#include "event_queue.hh"
#include "../logger/writer.hh"
#include "../time_util.hh"
#include "event_listener.hh"
#include "event_object.hh"
#include <algorithm>

namespace kratos { namespace corelib {

EventQueue::EventQueue() {
}

EventQueue::~EventQueue() {
    timerQueue_.stop();
    for (auto it : timerEventMap_) {
        it.second->dispose();
    }
}

void EventQueue::update() {
    if (!timerQueue_.isStarted()) {
        timerQueue_.start();
    }
    auto timerID = timerQueue_.checkExpired();
    while (timerID != INVALID_TIMER_ID) {
        auto it = timerEventMap_.find(timerID);
        if (it != timerEventMap_.end()) {
            auto eo = it->second;
            if (eo->isCancelled()) {
                timerEventMap_.erase(it);
                eo->dispose();
            } else {
                if (eo->getReceiver()) {
                    auto listenerIt = listenerMap_.find(eo->getReceiver());
                    if (listenerIt == listenerMap_.end()) {
                        EVENT_QUEUE_LOG_ERROR("Receiver not found:", eo->getReceiver());
                    } else {
                        try {
                            if (!listenerIt->second->handleEvent(eo)) {
                                EVENT_QUEUE_LOG_ERROR("Event process failed, event ID:", eo->getEventID());
                            }
                        } catch (std::exception& e) {
                            EVENT_QUEUE_LOG_EXCEPTION(e, "event ID:", eo->getEventID());
                        }
                    }
                } else {
                    // TODO performance issue:find it first
                    for (auto listener : eventHandlerMap_[eo->getEventID()]) {
                        try {
                            if (!listener->handleEvent(eo)) {
                                EVENT_QUEUE_LOG_ERROR("Event process failed, event ID:", eo->getEventID());
                            }
                        } catch (std::exception& e) {
                            EVENT_QUEUE_LOG_EXCEPTION(e, "event ID:", eo->getEventID());
                        }
                    }
                }
                if (!eo->isPeriod()) {
                    timerEventMap_.erase(it);
                    eo->dispose();
                }
            }            
        } else {
            EVENT_QUEUE_LOG_ERROR("Timer event not found, timer ID:", timerID);
            timerQueue_.cancel(timerID);
        }
        timerID = timerQueue_.checkExpired();
    }
}

bool EventQueue::addListener(EventID eventID, EventListener * listener) {
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!listener);

    if (eventID) {
        auto& list = eventHandlerMap_[eventID];
        auto it = std::find(list.begin(), list.end(), listener);
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(it != list.end());
        list.push_back(listener);
    }
    if (listener->getID()) {
        listenerMap_[listener->getID()] = listener;
    }
    return true;
}

bool EventQueue::removeListener(EventID eventID, EventListener * listener) {
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!listener);

    if (eventID) {
        auto& list = eventHandlerMap_[eventID];
        auto it = std::find(list.begin(), list.end(), listener);
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(it == list.end());
        list.erase(it);
    }
    if (listener->getID()) {
        listenerMap_.erase(listener->getID());
    }
    return true;
}

bool EventQueue::removeAllListener(EventID eventID) {
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eventID);

    for (auto listener : eventHandlerMap_[eventID]) {
        listenerMap_.erase(listener->getID());
    }
    eventHandlerMap_[eventID].clear();
    return true;
}

const ListenerList & EventQueue::getListeners(EventID eventID) {
    static ListenerList NullList;
    EVENT_QUEUE_PARAM_FAIL_RETURN(!eventID, NullList);

    auto it = eventHandlerMap_.find(eventID);
    if (it != eventHandlerMap_.end()) {
        return it->second;
    }
    return NullList;
}

EventListener * EventQueue::getListener(std::uint64_t receiver) {
    EVENT_QUEUE_PARAM_FAIL_RETURN(!receiver, nullptr);

    auto it = listenerMap_.find(receiver);
    if (it == listenerMap_.end()) {
        return nullptr;
    }
    return it->second;
}

bool EventQueue::fireEvent(EventObject& eo) {
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(eo.isCancelled());
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(eo.isFired());
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo.getEventID());

    if (!timerQueue_.isStarted()) {
        timerQueue_.start();
    }
    if (eo.getReceiver()) {
        auto it = listenerMap_.find(eo.getReceiver());
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(it == listenerMap_.end());
        eo.fire();
        try {
            return it->second->handleEvent(&eo);
        } catch (std::exception& e) {
            EVENT_QUEUE_LOG_EXCEPTION(e, "event ID:", eo.getEventID());
            return false;
        }
    } else {
        bool done = true;
        for (auto listener : eventHandlerMap_[eo.getEventID()]) {
            eo.fire();
            try {
                if (!listener->handleEvent(&eo)) {
                    EVENT_QUEUE_LOG_ERROR("process event failed:", eo.getEventID());
                    done = false;
                }
            } catch (std::exception& e) {
                EVENT_QUEUE_LOG_EXCEPTION(e, "event ID:", eo.getEventID());
                return false;
            }
        }
        return done;
    }
}

bool EventQueue::fireEvent(EventObject* eo, std::time_t timeout) {
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo);
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(eo->isCancelled());
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(eo->isFired());
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo->getEventID());
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!timeout);

    if (!timerQueue_.isStarted()) {
        timerQueue_.start();
    }
    eo->setTimerQueue(&timerQueue_);
    TimerID timerID = INVALID_TIMER_ID;
    if (eo->isPeriod()) {
        timerID = timerQueue_.startTimer(timeout, TimeUtil::getMillionSecondsTimestampOS());
    } else {
        timerID = timerQueue_.startTimerOnce(timeout, TimeUtil::getMillionSecondsTimestampOS());
    }
    EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(timerID == INVALID_TIMER_ID);
    eo->setTimerID(timerID);
    eo->fire();
    timerEventMap_[timerID] = eo;
    return true;
}

using QueueMap = std::unordered_map<std::string, EventQueue*>;
static QueueMap queueMap_;

EventQueue* EventQueue::getEventQueue(const std::string & name) {
    EVENT_QUEUE_PARAM_FAIL_RETURN(name.empty(), nullptr);
    
    auto it = queueMap_.find(name);
    if (it == queueMap_.end()) {
        auto eventQueue = new EventQueue();
        queueMap_.emplace(name, eventQueue);
        return eventQueue;
    }
    return it->second;
}

void EventQueue::updateEventQueue() {
    for (auto it : queueMap_) {
        it.second->update();
    }
}

}}
